package com.reviling.filamentandroid

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PointF
import android.opengl.Matrix
import android.util.Log
import android.view.Choreographer
import android.view.MotionEvent
import android.view.SurfaceView
import android.view.View
import com.google.android.filament.Colors
import com.google.android.filament.EntityManager
import com.google.android.filament.LightManager
import com.google.android.filament.Skybox
import com.google.android.filament.utils.KTX1Loader
import com.google.android.filament.utils.ModelViewer
import com.google.android.filament.utils.Utils
import java.nio.ByteBuffer
import java.util.Arrays
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit

/**
 * 3D图控制组件
 */
class CustomViewer : View.OnTouchListener {
    companion object {
        init {
            Utils.init()
        }
    }

    private lateinit var choreographer: Choreographer
    private lateinit var modelViewer: ModelViewer

    /**
     * 新增的几个参数
     */
    //横向旋转
    private var lateralDegrees = 0.0f

    //左右
    private var pitchLRDegrees = 0.0f

    //上下
    private var pitchUDDegrees = 0.0f


    @Volatile
    private var realLRPitchDegrees = 0.0f

    @Volatile
    private var realUDPitchDegrees = 0.0f

    @Volatile
    private var realLateralDegrees = 0.0f

    private var lateralEnable = true
    private var pitchEnable = true
    private var zoom: IntArray? = null
    private var cut: IntArray? = null
    private var lampshade: IntArray? = null
    private var barndoor: IntArray? = null
    private var modifyConnect: IntArray? = null
    private val lastPoint = PointF()
    private val thisPoint = PointF()
    private var scheduledExecutor: ScheduledExecutorService? = null
    private var cacheExecutor = Executors.newCachedThreadPool()
    private var onTouchDireListener: OnTouchDireListener? = null

    fun loadEntity() {
        choreographer = Choreographer.getInstance()
    }

    @SuppressLint("ClickableViewAccessibility")
    fun setSurfaceView(mSurfaceView: SurfaceView) {
        modelViewer = ModelViewer(mSurfaceView)
        //这个是把事件给到modelViewer中的ontouchEvent，那个事交给手势处理的，可以360无限旋转任意角度，但我们这里不需要全部角度，
        // 只需要部分角度，所以用下面的自定义（this）到本页面去处理
//        mSurfaceView.setOnTouchListener(modelViewer)
        mSurfaceView.setOnTouchListener(this)

        //Skybox and background color
        //without this part the scene'll appear broken
//        modelViewer.scene.skybox = Skybox.Builder().build(modelViewer.engine)
//        modelViewer.scene.skybox?.setColor(1.0f, 1.0f, 1.0f, 1.0f) //White color

    }


    fun loadDefault() {
        val entitiesByPrefix = modelViewer.asset!!.getEntitiesByPrefix("水平定位")
        val entitiesByPrefix2 = modelViewer.asset!!.getEntitiesByPrefix("俯仰定位")
        modelViewer.scene.removeEntities(entitiesByPrefix)
        modelViewer.scene.removeEntities(entitiesByPrefix2)
        setDefault()
    }


    fun loadGlb(context: Context, name: String) {
        val buffer = readAsset(context, "models/${name}.glb")
        modelViewer.apply {
            loadModelGlb(buffer)
            transformToUnitCube()
        }
    }

    fun loadGlb(context: Context, dirName: String, name: String) {
        val buffer = readAsset(context, "models/${dirName}/${name}.glb")
        modelViewer.apply {
            loadModelGlb(buffer)
            transformToUnitCube()
        }
    }

    fun loadGltf(context: Context, name: String) {
        val buffer = context.assets.open("models/${name}.gltf").use { input ->
            val bytes = ByteArray(input.available())
            input.read(bytes)
            ByteBuffer.wrap(bytes)
        }
        modelViewer.apply {
            loadModelGltf(buffer) { uri -> readAsset(context, "models/$uri") }
            transformToUnitCube()
        }
    }

    fun loadGltf(context: Context, dirName: String, name: String) {
        val buffer = context.assets.open("models/${dirName}/${name}.gltf").use { input ->
            val bytes = ByteArray(input.available())
            input.read(bytes)
            ByteBuffer.wrap(bytes)
        }
        modelViewer.apply {
            loadModelGltf(buffer) { uri -> readAsset(context, "models/${dirName}/$uri") }
            transformToUnitCube()
        }
    }

    fun loadIndirectLight(context: Context, ibl: String) {
        // Create the indirect light source and add it to the scene.
        val buffer = readAsset(context, "environments/default_env/${ibl}_ibl.ktx")
        KTX1Loader.createIndirectLight(modelViewer.engine, buffer).apply {
            intensity = 50_000f
            modelViewer.scene.indirectLight = this
        }
    }


    fun setDefault() {
        this.zoom = modelViewer.asset!!.getEntitiesByPrefix("zoom")
        this.cut = modelViewer.asset!!.getEntitiesByPrefix("cut")
        this.barndoor = modelViewer.asset!!.getEntitiesByPrefix("barndoor")//挡光板
        this.modifyConnect = modelViewer.asset!!.getEntitiesByPrefix("附件")
        this.lampshade = modelViewer.asset!!.getEntitiesByPrefix("灯罩")
        modelViewer.scene.removeEntities(this.zoom)
        modelViewer.scene.removeEntities(this.cut)
        modelViewer.scene.removeEntities(this.barndoor)
        modelViewer.scene.removeEntities(this.modifyConnect)
        modelViewer.scene.addEntities(this.lampshade)
    }

    fun addZoom() {
        Log.i("变换", "setZoom")
        modelViewer.scene.addEntities(this.zoom)
        modelViewer.scene.addEntities(this.modifyConnect)
        modelViewer.scene.removeEntities(this.lampshade)
        modelViewer.transformToUnitCube()
    }

    fun removeZoom() {
        modelViewer.scene.removeEntities(this.zoom)
        modelViewer.scene.removeEntities(this.modifyConnect)
        modelViewer.scene.addEntities(this.lampshade)
        modelViewer.transformToUnitCube()
    }

    fun addCut() {
        modelViewer.scene.addEntities(this.modifyConnect)
        modelViewer.scene.removeEntities(this.lampshade)
        modelViewer.scene.addEntities(this.cut)
        modelViewer.transformToUnitCube()
    }

    fun addBarnDoor() {
        modelViewer.scene.addEntities(this.barndoor)
        modelViewer.scene.removeEntities(this.lampshade)
        modelViewer.scene.addEntities(this.modifyConnect)
        modelViewer.transformToUnitCube()
    }


    fun setLateralEnable(z: Boolean) {
        this.lateralEnable = z
    }

    fun setPitchEnable(z: Boolean) {
        this.pitchEnable = z
    }

    /**
     * z：true是高光，false低光
     */
    fun loadLight(z: Boolean) {
        modelViewer.scene.removeEntity(modelViewer.light)
        val create = EntityManager.get().create()
        val cct: FloatArray = Colors.cct(6500.0f)
        LightManager.Builder(LightManager.Type.SUN).color(cct[0], cct[1], cct[2])
            .intensity(100000.0f)
//            .direction(if (z) -1.0f else 0.0f, -6.0f, 3.0f)
            //x轴决定的投影的亮光是否在，负数则不再，正数则在
            //y轴则和z轴的投影光亮一样，绝对值越大则越明显
            //z轴这个偏移给负数就会让道具变亮可见度高，给接近或者正数就会变暗，而且负数数值的绝对值（abs）越大，则反光调节时会越明显
            //一般-1不明显，-2开始到-10一般都很明显了
            .direction(if (z) -1.0f else 0.0f, -6f, -300f)//-300（-256其实就封顶了）
            .castLight(true)//投射光的效果加强
            .build(
                modelViewer.engine, create
            )
        modelViewer.scene.addEntity(create)
    }

    fun loadEnviroment(context: Context, ibl: String) {
        // Create the sky box and add it to the scene.
        val buffer = readAsset(context, "environments/default_env/${ibl}_skybox.ktx")
        KTX1Loader.createSkybox(modelViewer.engine, buffer).apply {
            modelViewer.scene.skybox = this
        }
    }

    fun loadSkybox() {
        modelViewer.scene.skybox = Skybox.Builder().build(modelViewer.engine)
//        modelViewer.scene.skybox?.setColor(0.04f, 0.04f, 0.04f, 1.0f)//黑色
//        modelViewer.scene.skybox?.setColor(1.0f, 1.0f, 1.0f, 1.0f) //白色
        modelViewer.scene.skybox?.setColor(0.6f, 0.6f, 0.6f, 1.0f) //白色
    }

    private fun readAsset(context: Context, assetName: String): ByteBuffer {
        val input = context.assets.open(assetName)
        val bytes = ByteArray(input.available())
        input.read(bytes)
        return ByteBuffer.wrap(bytes)
    }

    /**
     * 设置角度
     * f全部旋转  f2:前板上下俯仰  f3前板左右俯仰
     */
    fun setDegrees(f: Float, f2: Float, f3: Float) {
        Log.i("变换", "setDegrees: realLateral = $f, realPitch = $f2")
        this.lateralDegrees = -f
        this.pitchUDDegrees = -pitchTransform(f2)
        this.pitchLRDegrees = -pitchTransform(f3)
        this.realUDPitchDegrees = f2
        this.realLRPitchDegrees = f3
        this.realLateralDegrees = f
        setLampHolderRotation(this.lateralDegrees)
//        setLampHolderPitch(this.pitchUDDegrees, pitchLRDegrees)//默认只支持上下俯仰
//        setLampBody(this.lateralDegrees, this.pitchUDDegrees)
        setLampHolderPitch(pitchUDDegrees, pitchLRDegrees)
    }


    /**
     * 设置灯的控制旋转,可以看看Float3，也是属于filament库的，用它来做个写法看看
     */
    private fun setLampHolderRotation(f: Float) {
        Log.i("变换", "setLampHolder: lateralDegrees = $f")
        val transformManager = modelViewer.engine.transformManager
        val entitiesByName = modelViewer.asset!!.getEntitiesByName("group1")//全部
        if (entitiesByName.isNotEmpty()) {
            val instance = transformManager.getInstance(entitiesByName[0])
            val fArr = FloatArray(16)
            transformManager.getTransform(instance, fArr)
            val fArr2 = FloatArray(16)
            Matrix.setRotateM(fArr2, 0, f, 0.0f, 1.0f, 0.0f)
            fArr2[12] = fArr[12]
            fArr2[13] = fArr[13]
            fArr2[14] = fArr[14]
            transformManager.setTransform(instance, fArr2)
        } else Log.d("变换", "找不到group1")
    }

    /**
     * 设置灯的控制俯仰,只能控制左右/上下
     * isLeftRight：是否是左右俯仰，否则就是上下俯仰
     */
    private fun setLampHolderPitchSingleDire(f2: Float, isLeftRight: Boolean) {
        Log.i("变换", "setLampHolder: pitchDegrees = $f2")
        val transformManager = modelViewer.engine.transformManager
        val entitiesByName = modelViewer.asset!!.getEntitiesByName("pt")//前板
        if (entitiesByName.isNotEmpty()) {
            val instance = transformManager.getInstance(entitiesByName[0])
            val fArr = FloatArray(16)
            transformManager.getTransform(instance, fArr)
            val fArr2 = FloatArray(16)
            Matrix.setRotateM(
                fArr2,
                0,
                f2,
                if (isLeftRight) 0.0f else 1.0f,
                if (isLeftRight) 1.0f else 0.0f,
                0.0f
            )
            fArr2[12] = fArr[12]
            fArr2[13] = fArr[13]
            fArr2[14] = fArr[14]
            transformManager.setTransform(instance, fArr2)
        } else Log.d("变换", "找不到pt")
    }

    /**
     * 控制单元素（组）的上下左右变换的
     */
    private fun setLampHolderPitch(f: Float, f2: Float) {
        val str: String = "变换"
        Log.i(str, "setLampBody2: pitchUDDegrees = $f")
        Log.i(str, "setLampBody2: pitchLRDegrees = $f2")
        val transformManager = modelViewer.engine.transformManager
        val entitiesByName = modelViewer.asset!!.getEntitiesByName("pt")
        if (entitiesByName.isNotEmpty()) {
            val fArr = FloatArray(16)//要保留第一个
            val instance = transformManager.getInstance(entitiesByName[0])
            val fArr2 = FloatArray(16)//第二个填充给getTransform
            transformManager.getTransform(instance, fArr2)//所以fArr2只是用来给原始数据填充的
            Log.i(str, "setLampBody: fixMatrix = " + Arrays.toString(fArr))
            Log.i(str, "setLampBody: curMatrix = " + Arrays.toString(fArr2))
            val fArr3 = FloatArray(16)
            Matrix.setRotateM(fArr3, 0, f, 1.0f, 0.0f, 0.0f)
            fArr3[12] = fArr[12] - fArr[12] * sinF(f) * 5.0f//这里必须用fArr计算，否则会出现飘移
            val abs = Math.abs(-3.44173952).toFloat()
            fArr3[13] = sinF(f2) * abs + 2.7232416f
            fArr3[14] = -3.7950952f - cosF(f2) * abs + abs
            val fArr4 = FloatArray(16)
            Matrix.setRotateM(fArr4, 0, f2, 0.0f, 1.0f, 0.0f)
            val fArr5 = FloatArray(16)
            Matrix.multiplyMM(fArr5, 0, fArr4, 0, fArr3, 0)
            transformManager.setTransform(instance, fArr5)
        } else Log.d("变换", "pt")
    }



    /**
     * 设置灯体整体变换俯仰，两个组元素一起动
     */
    private fun setLampBody(f: Float, f2: Float) {
        val str: String = "变换"
        Log.i(str, "setLampBody: lateralDegrees = $f")
        Log.i(str, "setLampBody: pitchDegrees = $f2")
        val transformManager = modelViewer.engine.transformManager
        val entitiesByName = modelViewer.asset!!.getEntitiesByName("group1")
        val entitiesByName2 = modelViewer.asset!!.getEntitiesByName("pt")
        if (entitiesByName.isNotEmpty() && entitiesByName2.isNotEmpty()) {
            val fArr = FloatArray(16)
            transformManager.getTransform(transformManager.getInstance(entitiesByName2[0]), fArr)
            val instance = transformManager.getInstance(entitiesByName[0])
            val fArr2 = FloatArray(16)
            transformManager.getTransform(instance, fArr2)
            Log.i(str, "setLampBody: fixMatrix = " + Arrays.toString(fArr))
            Log.i(str, "setLampBody: curMatrix = " + Arrays.toString(fArr2))
            val fArr3 = FloatArray(16)
            Matrix.setRotateM(fArr3, 0, f2, 1.0f, 0.0f, 0.0f)
            fArr3[12] = fArr[12] - fArr[12] * sinF(f) * 5.0f
            val abs = Math.abs(-3.44173952).toFloat()
            fArr3[13] = sinF(f2) * abs + 2.7232416f
            fArr3[14] = -3.7950952f - cosF(f2) * abs + abs
            val fArr4 = FloatArray(16)
            Matrix.setRotateM(fArr4, 0, f, 0.0f, 1.0f, 0.0f)
            val fArr5 = FloatArray(16)
            Matrix.multiplyMM(fArr5, 0, fArr4, 0, fArr3, 0)
            transformManager.setTransform(instance, fArr5)
        } else Log.d("变换", "找不到灯体和附件以及俯仰定位")
    }

    /**
     * 俯仰变换
     */
    private fun pitchTransform(f: Float): Float {
        val f2 = f - 90.0f
        return f2
    }


    private fun sinF(f: Float): Float {
        return Math.sin(Math.toRadians(f.toDouble())).toFloat()
    }

    private fun cosF(f: Float): Float {
        return Math.cos(Math.toRadians(f.toDouble())).toFloat()
    }


    private val frameCallback = object : Choreographer.FrameCallback {
        private val startTime = System.nanoTime()
        override fun doFrame(currentTime: Long) {
            val seconds = (currentTime - startTime).toDouble() / 1_000_000_000
            choreographer.postFrameCallback(this)
            modelViewer.animator?.apply {
                if (animationCount > 0) {
                    applyAnimation(0, seconds.toFloat())
                }
                updateBoneMatrices()
            }
            modelViewer.render(currentTime)
        }
    }

    fun onResume() {
        choreographer.postFrameCallback(frameCallback)
    }

    fun onPause() {
        choreographer.removeFrameCallback(frameCallback)
    }

    fun onDestroy() {
        choreographer.removeFrameCallback(frameCallback)
        cacheExecutor = null
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(view: View?, motionEvent: MotionEvent): Boolean {
        Log.i("变换", "onTouch: getAction = " + motionEvent.action)
        Log.i("变换", "onTouch: getActionMasked = " + motionEvent.actionMasked)
        if (!lateralEnable && !pitchEnable) {
            return false
        }
        val actionMasked = motionEvent.actionMasked
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            this.lastPoint.set(motionEvent.x, motionEvent.y)
            this.thisPoint.set(motionEvent.x, motionEvent.y)
            this.onTouchDireListener?.onTouchStart()
        } else if (actionMasked != MotionEvent.ACTION_MOVE) {
            this.lastPoint.set(0.0f, 0.0f)
            this.thisPoint.set(0.0f, 0.0f)
            onTouchDireListener?.onTouchStop()
            stopMove()
        } else {
            this.thisPoint.set(motionEvent.x, motionEvent.y)
            if (lateralEnable) {
                val f: Float = this.thisPoint.x - this.lastPoint.x
                if (onTouchDireListener != null) {
                    realLateralDegrees = this.onTouchDireListener!!.onTouchDire(true, 0, f)
                }
                Log.i("变换", "xDire = $f")
            }
            if (pitchEnable) {
                val f2: Float = this.thisPoint.y - this.lastPoint.y
                if (onTouchDireListener != null) {
                    realLRPitchDegrees = this.onTouchDireListener!!.onTouchDire(false, 2, f2)
                }//俯仰这里手势滑动的就只有上下了，毕竟左右已经和旋转冲突了，不会起作用的
                Log.i("变换", "yDire = $f2")
            }
            this.lastPoint.set(motionEvent.x, motionEvent.y)
            setDegrees(realLateralDegrees, realUDPitchDegrees, realLRPitchDegrees)
            startMove()
        }
        return true
    }


    private fun startMove() {
        if (this.scheduledExecutor == null) {
            val newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor()
            this.scheduledExecutor = newSingleThreadScheduledExecutor
            newSingleThreadScheduledExecutor.scheduleWithFixedDelay(
                {
                    this.onTouchPer200ms()
                }, 0, 200, TimeUnit.MILLISECONDS
            )
        }
    }

    private fun stopMove() {
        this.cacheExecutor.execute(Runnable {
            this.clearExecutor()
        })
    }

    private fun onTouchPer200ms() {
        onTouchDireListener?.onTouchPer200ms(realLateralDegrees, realLRPitchDegrees)
    }

    private fun clearExecutor() {
        try {
            Thread.sleep(700)
            val scheduledExecutorService = scheduledExecutor
            if (scheduledExecutorService != null) {
                scheduledExecutorService.shutdownNow()
                scheduledExecutor = null
            }
        } catch (e: InterruptedException) {
            throw RuntimeException(e)
        }
    }

    fun setOnTouchDireListener(onTouchDireListener: OnTouchDireListener) {
        this.onTouchDireListener = onTouchDireListener
    }

    interface OnTouchDireListener {
        //isRotation：是否是旋转状态，true时一般moveStatus = 0
        //moveStatus:0非俯仰的状态，1左右俯仰，2上下俯仰
        fun onTouchDire(isRotation: Boolean, moveStatus: Int, f: Float): Float
        fun onTouchPer200ms(f: Float, f2: Float)
        fun onTouchStart()
        fun onTouchStop()
    }
}